出处:掘金
原作者:ErpanOmer
如果你曾在浏览器中使用 Web Worker 或 Node.js 的 Worker Thread 编写多线程代码,那么你一定遇到过这两个概念:
ArrayBufferSharedArrayBuffer它们的名字看起来只差一个“Shared”,实则背后承载的是线程安全模型、内存隔离策略,甚至涉及到了浏览器的安全沙箱机制
本篇文章将带你深入探索:
ArrayBuffer 与 SharedArrayBuffer 的设计目的JavaScript 的运行模型是单线程的,意味着同一时间只能执行一段代码。为了避免 UI 卡顿,HTML5 引入了 Web Worker(浏览器)和 Worker Thread(Node.js),用来并行执行任务
问题是,多线程就需要解决一个问题:数据怎么传?
postMessage()但 postMessage() 是通过结构化克隆算法来传输数据的:
ArrayBuffer 是 JavaScript 操作二进制数据的底层数据结构。它提供了一段连续的、固定长度的原始内存,用于存储数据
const buffer = new ArrayBuffer(8); // 8 字节
const view = new Uint8Array(buffer);
view[0] = 42;
特点:
TypedArray(如 Uint8Array , Float32Array)创建视图postMessage() 传入 Worker,但默认是复制worker.postMessage(buffer, [buffer]); // Transfer
这个“可转移对象机制”是一种内存拥有权的转移,不是共享。发送方再想访问?对不起,byteLength = 0
所以 ArrayBuffer 是一种“可搬家,但不共居”的模型
SharedArrayBuffer 的出现正是为了解决上述问题:在多个线程间共享一块内存,同时保持数据一致性
const shared = new SharedArrayBuffer(8);
const view = new Int32Array(shared);
view[0] = 123;
可以把它传给多个 Worker,无需复制,大家直接指向同一块物理内存
worker.postMessage(shared); // 不需要 transfer 列表
| 特性 | ArrayBuffer | SharedArrayBuffer |
|---|---|---|
| 是否共享 | 否(复制/转移) | 是(直接引用) |
| 是否可转移 | 是 | 否(共享无须转移) |
| 可否在多个线程访问 | 否 | 是(并发访问) |
| 是否线程安全 | 否 | 否(需配合 Atomics) |
| 是否可直接修改 | 是 | 是 |
注意:SharedArrayBuffer 本身并不保证线程安全,你仍需使用 Atomics 提供的原子操作
当你通过 postMessage() 传递 ArrayBuffer:
这是浏览器为 JS 实现的“堆隔离”策略 —— 避免线程交叉污染
SharedArrayBuffer 本质上由浏览器的底层内存模型支持:
更关键的是,它跳过了结构化克隆的隔离机制,完全依赖开发者自己控制一致性
一个关键词:Spectre
Spectre 是一种处理器层面的旁路攻击漏洞,攻击者可以利用预测执行 + 缓存时序分析,越权读取别的线程的内存数据
SharedArrayBuffer 刚好是“极佳入口”:
于是浏览器在 2018 年紧急禁用了它,直到提供了对应的安全缓解机制:
配置如下 HTTP Header 才能启用 SharedArrayBuffer:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
你可以在浏览器控制台看到报错提示,不配这个头,SharedArrayBuffer 根本不能用
SharedArrayBuffer 让线程能访问同一内存,但会有竞争条件(Race Condition)
举例:
// 两个线程同时执行
view[0] += 1;
可能在底层变成:
view[0] = 1view[0] = 1222,但我们期望是 3解决办法是使用 Atomics:
Atomics.add(view, 0, 1); // 原子加 1
它保证了内存操作的原子性,底层依赖 CPU 的原子指令(如 x86 的 LOCK 前缀)
你还能用:
Atomics.wait() + Atomics.notify() 实现同步阻塞Atomics.compareExchange() 实现锁竞争Atomics.store/load 实现可见性保障这是 JavaScript 走向并发编程的重要一步
主线程:
const shared = new SharedArrayBuffer(4); // 4 字节 = 1 个 Int32
const counter = new Int32Array(shared);
const worker = new Worker('./worker.js');
worker.postMessage(shared);
worker.js:
onmessage = function(e) {
const counter = new Int32Array(e.data);
for (let i = 0; i < 10000; i++) {
Atomics.add(counter, 0, 1);
}
postMessage('done');
}
这个方式可以安全地在多个线程对同一个变量进行累加操作,结果是稳定的、不会丢失
SharedArrayBuffer 与 Atomics 已经是 JS 并发编程的重要组成部分。未来的方向包括:
SharedArrayBuffer 进行跨模块内存共享Comlink、Threads.js)会对其做封装但与此同时,安全机制始终是底线,未来浏览器将持续强化隔离能力和 CSP 限制
ArrayBuffer 是单线程内存数据容器,可传可转,但不共享SharedArrayBuffer 是多线程共享内存工具,但需开发者确保一致性Atomics 提供原子性保障